package com.threefivework.mymall.util;

import com.google.common.collect.Maps;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import com.thoughtworks.xstream.security.NullPermission;
import com.thoughtworks.xstream.security.PrimitiveTypePermission;
import com.threefivework.mymall.model.domain.TradeDTO;
import com.threefivework.mymall.wechat.constant.WXConfigConstants;
import com.threefivework.mymall.wechat.constant.WXPayConstants;
import com.threefivework.mymall.wechat.util.WXPayUtil;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author yuanshi.fu
 * @description:
 * @date 2018/6/26 下午12:37
 */
public class XmlUtil {

    private static final Logger logger = LoggerFactory.getLogger(XmlUtil.class);

    private static final Map<String, Field> fieldCache = new ConcurrentHashMap<String, Field>();

    public static String bean2Xml(Object object) {
//        XStream xStream = new XStream(new DomDriver("utf8"));
        XStream xStream = new XStream(new DomDriver("utf8"));
        XStream xStream2 = new XStream(new PureJavaReflectionProvider(){

        });
        //设置reference模型
        //xstream.setMode(XStream.NO_REFERENCES);//不引用
        xStream.setMode(XStream.NO_REFERENCES);//id引用
        //以压缩的方式输出XML
//        StringWriter writer = null;
        try {
            //识别对象的注解
            xStream.processAnnotations(object.getClass());
            //以压缩的方式输出XML
//            writer = new StringWriter();
//            xStream.marshal(object, new CompactWriter(writer));
        } catch (Exception e) {
            logger.error("[XmlUtil] xx2Xml异常", e);
        } finally {
//            IOUtils.closeQuietly(writer);
        }

        return xStream.toXML(object);
//        return writer.toString();
    }

    public static <T> T xml2Bean(String xml, Class<T> t) {
        XStream xStream = new XStream(new DomDriver());
        xStream.processAnnotations(t);
        return (T)xStream.fromXML(xml, t);
    }

    public class CDataAdapter extends XmlAdapter<String, String> {
        @Override
        public String marshal (String v) throws Exception
        {
            return "<![CDATA[" + v + "]]>";
        }
        @Override
        public String unmarshal (String v) throws Exception
        {
            if ("<![CDATA[]]>".equals (v))
            {
                return "";
            }
            String v1 = null;
            String v2 = null;
            String subStart = "<![CDATA[";
            int a = v.indexOf (subStart);
            if (a >= 0)
            {
                v1 = v.substring (subStart.length (), v.length ());
            }
            else
            {
                return v;
            }
            String subEnd = "]]>";
            int b = v1.indexOf (subEnd);
            if (b >= 0)
            {
                v2 = v1.substring (0, b);
            }
            return v2;
        }
    }

    private static String PREFIX_CDATA = "<![CDATA[";
    private static String SUFFIX_CDATA = "]]>";

    /**
     * 全部转化
     */
    public static XStream initXStream() {
        return new XStream(new XppDriver() {
            @Override
            public HierarchicalStreamWriter createWriter(Writer out) {
                return new PrettyPrintWriter(out) {
                    @Override
                    protected void writeText(QuickWriter writer, String text) {
                        // if (text.startsWith(PREFIX_CDATA) &&
                        // text.endsWith(SUFFIX_CDATA)) {
                        writer.write(PREFIX_CDATA + text + SUFFIX_CDATA);
                        // } else {
                        // super.writeText(writer, text);
                        // }
                    }
                };
            }
        });
    }

    /**
     * 初始化XStream可支持某一字段可以加入CDATA标签,如果需要某一字段使用原文,就需要在String类型的text的头加上
     * "<![CDATA["和结尾处加上"]]>"标签， 以供XStream输出时进行识别
     * @param isAddCDATA 是否支持CDATA标签
     */
    public static XStream initXStream(boolean isAddCDATA) {
        XStream xstream = null;
        if (isAddCDATA) {
            xstream = new XStream(new XppDriver() {
                @Override
                public HierarchicalStreamWriter createWriter(Writer out) {
                    return new PrettyPrintWriter(out) {
                        protected void writeText(QuickWriter writer, String text) {
                            if (text.startsWith(PREFIX_CDATA) && text.endsWith(SUFFIX_CDATA)) {
                                writer.write(text);
                            } else {
                                super.writeText(writer, text);
                            }
                        }
                    };
                }
            });
        } else {
            xstream = new XStream();
        }
        return xstream;
    }

    public static XStream getInstance() {
        XStream xstream = new XStream(new PureJavaReflectionProvider(), new XppDriver() {

            @Override
            public HierarchicalStreamWriter createWriter(Writer out) {
                return new PrettyPrintWriter(out, getNameCoder()) {
                    protected String PREFIX_CDATA = "<![CDATA[";
                    protected String SUFFIX_CDATA = "]]>";
                    protected String PREFIX_MEDIA_ID = "<MediaId>";
                    protected String SUFFIX_MEDIA_ID = "</MediaId>";

                    @Override
                    protected void writeText(QuickWriter writer, String text) {
                        if (text.startsWith(this.PREFIX_CDATA) && text.endsWith(this.SUFFIX_CDATA)) {
                            writer.write(text);
                        } else if (text.startsWith(this.PREFIX_MEDIA_ID) && text.endsWith(this.SUFFIX_MEDIA_ID)) {
                            writer.write(text);
                        } else {
                            super.writeText(writer, text);
                        }

                    }

                    /**
                     * 防止将_转换成__
                     */
                    @Override
                    public String encodeNode(String name) {
                        return name;
                    }
                };
            }
        });

        xstream.ignoreUnknownElements();
        xstream.setMode(XStream.NO_REFERENCES);
        xstream.addPermission(NullPermission.NULL);
        xstream.addPermission(PrimitiveTypePermission.PRIMITIVES);
        return xstream;
    }

    /**
     * 处理 HTTPS API返回数据，转换成Map对象。return_code为SUCCESS时，验证签名。
     * @param xmlStr API返回的XML格式数据
     * @return Map类型数据
     * @throws Exception
     */
    public static Map<String, String> processResponseXml(String xmlStr) throws Exception {

        String return_code;
        Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
        if (respData.containsKey(WXPayConstants.FIELD_RETURN_CODE)) {
            return_code = respData.get(WXPayConstants.FIELD_RETURN_CODE);
        }
        else {
            throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
        }

        if (return_code.equals(WXPayConstants.FAIL)) {
            return respData;
        }
        else if (StringUtils.equals(return_code, WXPayConstants.SUCCESS)) {
            if (isResponseSignatureValid(respData)) {
                return respData;
            }
            else {
                throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
            }
        }
        else {
            throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
        }
    }

    /**
     * 判断xml数据的sign是否有效，必须包含sign字段，否则返回false。
     *
     * @param reqData 向wxpay post的请求数据
     * @return 签名是否有效
     * @throws Exception
     */
    public static boolean isResponseSignatureValid(Map<String, String> reqData) throws Exception {
        // 返回数据的签名方式和请求中给定的签名方式是一致的
        return WXPayUtil.isSignatureValid(reqData, WXConfigConstants.KEY, WXPayConstants.SignType.MD5);
    }

    /**
     * 将bean按照@XStreamAlias标识的字符串内容生成以之为key的map对象
     *
     * @param bean 包含@XStreamAlias的xml bean对象
     * @return map对象
     */
    public static Map<String, String> xmlBean2Map(Object bean) throws Exception {
        Map<String, String> result = new HashMap<>();
        List<Field> fields = new ArrayList<>(Arrays.asList(bean.getClass().getDeclaredFields()));
        fields.addAll(Arrays.asList(bean.getClass().getSuperclass().getDeclaredFields()));

        BeanInfo beanInfo = null;
        try {
            beanInfo = Introspector.getBeanInfo(bean.getClass());
        } catch (IntrospectionException e) {
            logger.error("获取bean Info失败,bean name:" + bean.getClass().getSimpleName(), e);
            throw new Exception("处理失败");
        }

        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
        boolean isAccessible;
        try {
            for (PropertyDescriptor pd : pds) {
                //无set方法的肯定不是需要的参数
                if (pd.getName().equals("class") || pd.getWriteMethod() == null) {
                    continue;
                }

                Field field = getField(bean.getClass(), pd);
                if (field == null) {
                    continue;
                }
                //对于复杂的对象类型，跳过，不放入map中
                if (!canConvert2SimpleStr(field.getType())) {
                    continue;
                }
                isAccessible = field.isAccessible();

                field.setAccessible(true);
                if (field.get(bean) == null) {
                    field.setAccessible(isAccessible);
                    continue;
                }

                if (field.isAnnotationPresent(XStreamAlias.class)) {
                    result.put(field.getAnnotation(XStreamAlias.class).value(), field.get(bean).toString());
                } else if (!Modifier.isStatic(field.getModifiers())) {
                    //忽略掉静态成员变量
                    result.put(field.getName(), field.get(bean).toString());
                }

                field.setAccessible(isAccessible);
            }
        } catch (Exception e) {
            logger.error("转换xml失败,bean name:" + bean.getClass().getSimpleName(), e);
            throw new Exception("处理失败");
        }

        return result;
    }

    public static Field getField(Class<?> clazz, PropertyDescriptor pd) {
        if (clazz.equals(Object.class)) {
            return null;
        }

        String key = new StringBuilder(clazz.getName()).append("_").append(pd.getName()).toString();
        Field field = fieldCache.get(key);
        if (field == null) {// 这个方法不加锁，初始化并发也没关系，无非多put几次

            try {
                field = clazz.getDeclaredField(pd.getName());
            } catch (NoSuchFieldException e) {
                field = getField(clazz.getSuperclass(), pd);
            }

            if (field != null) {
                fieldCache.put(key, field);
            }

        }
        return field;
    }

    /**
     * 判断是否是可以转换为简单的map中的string的值
     *
     * @param typeClass
     * @return
     */
    private static boolean canConvert2SimpleStr(Class typeClass) {
        if (String.class.isAssignableFrom(typeClass) ||
                Boolean.class.isAssignableFrom(typeClass) ||
                Number.class.isAssignableFrom(typeClass)) {
            return true;
        }

        return false;
    }

    public static void main(String[] args) {
        Map<String, String> data = Maps.newHashMap();
        data.put("appid", "123");
        data.put("sign", "sign");
        TradeDTO tradeDTO = new TradeDTO();
        tradeDTO.setName("test");

//        System.out.println(CheckUtil.checkIsRequired(test).toCompletableFuture().join());

        System.out.println(StringEscapeUtils.unescapeXml(XmlUtil.bean2Xml(tradeDTO)));
    }
}
